#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G16 -- S08 ------------------------------------------------------------##
__all__ = []


from .. import autostacks
from .. import comparecards as cmp_cards
from .. import formlib as layout
from .. import hint
from .. import pilelib as stack
from .. import stackfill as fill
from ..forms import (freecell as FreeCellLayout,
                     fortythieves as FortyThievesLayout,
                     klondike as KlondikeLayout,)
from ..gamelib import info as gi, state, game
from ..hint import AbstractHint, CautiousDefaultHint
from ..packlib import info as cs, place
from ..utillib.misc import kwdefault

GT, SL = gi.GT, gi.SL
Wrap = stack.StackWrapper


class Hint(AbstractHint):

    def compute_hints(self):
        """
        compute_hints() -> None

        Calculate and score possible moves.

        Overide main hint intelligence.
          - **dropstacks** drop/flip **rows**
        """
        # 1) **dropstacks** drop/flip **rows**
        self.step010(self._stacks['grp']['dropstacks'],
                     self._stacks['set']['reserves'])

    def step010(self, dropstacks, rows):
        """
        step010(dropstacks:seq(Stack ...), rows:seq(Stack ...)) -> None

        Calculate and score **dropstacks** drop/build **rows**. (60000 .. 99999)

        Override **dropstacks** drop **rows**, sum score based next possible
        drop/flip.
        """
        foundations = self._stacks['set']['foundations']
        for from_stack in dropstacks:
            to_stack, ncards = from_stack.cardsDrop(foundations)
            if not to_stack:
                continue
            assert ncards == 1  # Assertion must hold for Golf
            after_drop_to = hint.AClonedStack.clone(to_stack,
                                   cards=to_stack.cards + from_stack.cards[-1:])
            self._score = 10000 + from_stack.id
            # Check what drop does for game
            for stack in rows:
                if not stack.cards:
                    continue
                if stack is from_stack:
                    stack = hint.AClonedStack.clone(from_stack,
                                               cards=after_drop_from.cards[:-1])
                if stack.cardFlips():
                    self._score += 100
                elif stack.cardsDrop((after_drop,))[0]:
                    self._score += 100
            # add hint
            self.add_hint(ncards, from_stack, to_stack)


class Talon(stack.WasteTalon):

    def can_deal_cards(self):
        if not super(Talon, self).can_deal_cards():
            return False
        return not self.game.is_complete


class DropWasteStack(stack.SingleAsDoubleClickMethod, stack.WasteStack):
    pass


class WasteMethod(object):

    def __init__(self, x, y, game, strict=False, **cap):
        """
        __init__(x:int, y:int, game:obj[ offset:tup=(0, 0), strict:bool=False,
          *capability*{max_move=0}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :keyword tuple offset: offset from left and offset from top
        :keyword boolean strict: Not build off KING

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        self.__strict = strict
        kwdefault(cap, max_move=0, offset=(0, 0))
        super(WasteMethod, self).__init__(x, y, game, **cap)

    def _sequence(self, cards):
        """
        _sequence(cards:tup) -> bool

        Wrapper for parent _sequence() with dir *capability* set both
        negative and positive. When __strict no build on KING.

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean
        """
        if self.__strict and self.TOPCARD['rank'] == cs.KING:
            return False
        return super(WasteMethod, self)._sequence(cards)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return '{0} {1}'.format(_('Waste.'),
                                super(WasteMethod, self)._helpString)


class UD_RK_Waste(WasteMethod, stack.UD_RK_RowStack):
    pass


class UD_SS_Waste(WasteMethod, stack.UD_SS_RowStack):
    pass


class Waterfall_Foundation(stack.RK_Foundation):

    def isSequence(self, cards):
        """
        _sequence(cards:tup) -> bool

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean
        """
        if self.TOPCARD and self.TOPCARD['rank'] == cs.KING:
            return cmp_cards.isSuitSequence(cards, mod=4, dir=self.cap['dir'])
        return cmp_cards.isSameSuitSequence(cards, **self.cap)

    @state.not_state('is_preview')
    def _update_text(self):
        """
        Augement to update current suit for misc texts.

        .. \_update_text() -> None
        """
        super(Waterfall_Foundation, self)._update_text()
        if not len(self.cards) == self.game['ncards']:
            card, suit = self.TOPCARD, self.cap['basesuit']
            if card is not None:
                suit = card['suit'] + int(card['rank'] == cs.KING)
            f.texts['misc'].config(text=self.game.SUITS[suit % 4])


class DropFoundation(stack.RK_Foundation):

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check parent.cardsBuild() and cards isSequence() with top card
        acccording to *capabilities*.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        base_card = self.game.s['reserves'][0].TOPCARD
        if not base_card:
            return False
        self.cap['base_rank'] = (base_card['rank'] + self.cap['dir'])
        return super(DropFoundation, self).cardsBuild(from_stack, cards)

    @state.move_state('fill')
    def _auto_fill(self):
        """
        """
        if len(self.cards) == 12:
            self.s['reserves'][0].move_pile(1, self, frames=4)
            self.move_pile(13, self.s['foundations'][1], frames=4)


class RowStack(stack.SingleAsDoubleClickMethod, stack.BasicRowStack):
    pass


class BlackHole_RowStack(RowStack):
    _BOTTOM_IMAGE_ = 'shade'


class DiamondMine_RowStack(stack.RK_RowStack):

    def _sequence(self, cards):
        """
        _sequence(cards:tup) -> bool

        :param tuple cards: cards to accept from another pile
        :return: cards acceptable?
        :rtype: boolean
        """
        return (not cmp_cards.isSameSuit(cards, suit=3) and
                super(DiamondMine_RowStack, self)._sequence(cards))

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard a shade and is_filled on
        pile with max_cards.
        """
        RANKS = len(self.game['ranks'])
        if (len(self.cards) == RANKS and
            cmp_cards.isSameSuitSequence(self.cards)):
            self.is_filled = True


class Golf(game.Game):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 7 N. Init deal 5 card. Build none. Play top.

    _talon_
      Pile 1 SE. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone. Play
      none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫, ⟪dropstacks⟫ disabled.
    ------
    :cvar Stack _talon_: Talon
    :cvar Stack _waste_: UD_RK_Waste
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=36, name='Golf', game_type=(GT.GOLF | GT.POPULAR |
                    GT.GNOME | GT.KDE), decks=1, skill_level=SL.BALANCED,
                    version='2.90')
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = Talon
    _waste_ = UD_RK_Waste
    _row_ = RowStack
    _hint_ = Hint

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes alone.

        .. \_create_game(rows=7, playcards=5) -> Layout
        """
        rows = kw.get('rows', 7)
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + (rows + 1) * l.XS, l.YM + l.YS + l.TEXT_HEIGHT
                           + l._get_stack_height(kw.get('playcards', 5), l.YS),)
        s['rows'] =[self._row_(x, l.YM, game=self)
                    for x in l.row_iter(l.XM + l.HXS, npiles=rows)]
        x, y = l.XM, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'n')
        x += l.XS
        s['wastes'] = s['foundations'] = [self._waste_(x, y, game=self,
                              offset=(l._get_xoffset(self.width - 2 *l.XS), 0))]
        l.set_ncards_text(s['wastes'][-1], 'n')
        self.sg['openstacks'] = s['foundations']
        self.sg['Talons'] = [s['talon']]
        self.sg['dropstacks'] = s['rows']
        return l

    def _start(self, rows=4, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea open.

        .. \_start([rows=4]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()

    @property
    def is_complete(self):
        return all(not r.cards for r in self.s['rows'])

    @property
    def _highlight_piles(self):
        """
        Return piles to highlight for possible move.

        :return: empty.
        :rtype: tuple
        """
        return ()

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable quickplay when event, else autodrop, quickplay.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        piles = super(Golf, self)._autopiles(event)
        if event is None:
            return autostacks.flip(piles)
        else:
            return autostacks.flip_drop(piles)


class DeadKingGolf(Golf):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 7 N. Init deal 5 card. Build none. Play top.

    _talon_
      Pile 1 SE. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone with none
      on KING. Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫, ⟪dropstacks⟫ disabled.
    ------
    :cvar Stack _talon_: Talon
    :cvar Stack _waste_: UD_RK_Waste (strict=True)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=259, name='Dead King Golf', game_type=GT.GOLF,
                    decks=1, skill_level=SL.BALANCED,
                    version='2.01')
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _waste_ = Wrap(UD_RK_Waste, strict=True)


class RelaxedGolf(Golf):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 7 N. Init deal 5 card. Build none. Play top.

    _talon_
      Pile 1 SE. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone wrap.
      Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫, ⟪dropstacks⟫ disabled.
    ------
    :cvar Stack _talon_: Talon
    :cvar Stack _waste_: UD_RK_Waste (strict=True)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=260, name='Relaxed Golf', game_type=(GT.GOLF |
                    GT.RELAXED), decks=1, version='2.01',
                    skill_level=SL.BALANCED, altnames=('Putt Putt',
                    'Golf (Relaxed)',))
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _waste_ = Wrap(UD_RK_Waste, mod=13)


class BlackHole(game.Game):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 17 over 4 row with center skipped on row 2 3. Init deal 1 card.
      Build none. Play none.

    _foundation_
      Pile 1 center. Init deal base ACE card. Build either in rank alone wrap.
      Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.UD_RK_Foundation (mod=13, max_move=0,
      max_cards=52, suit=ANY)
    :cvar Stack _row_: BlackHole_RowStack (max_cards=3)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=98, name='Black Hole', game_type=(GT.GOLF | GT.OPEN),
                    decks=1, skill_level=SL.MOSTLY_SKILL,
                     version='3.00')
    __author__ = 'Markus Oberhumer'
    __credits__ = ['David Parlett']
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = Wrap(stack.UD_RK_Foundation, mod=13, max_move=0,
                                max_cards=52, suit=cs.ANY)
    _row_ = Wrap(BlackHole_RowStack, max_cards=3)
    _hint_ = Hint

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes alone.

        .. \_create_game(playcards=5) -> Layout
        """
        l, s = layout.Layout(self), self.s
        dx = max(2 * l.XS, l._get_stack_width(kw.get('playcards', 5, l.XS)))
        self.table_size = (l.XM + 5 * dx, l.YM + 4 * l.YS,)
        for yp, y in enumerate(l.col_iter(l.YM, npiles=4)):
            for xp, x in enumerate(l.row_iter(l.XM, npiles=5, dx=dx)):
                if xp == 2 and yp == 1:  # foundation center
                    s['foundations'] = [self._foundation_(x, y + l.HYS,
                                                          game=self)]
                elif xp == 4 and yp == 3: # talon SE corner
                    s['talon'] = self._talon_(x, y, game=self)
                    continue
                elif not (yp == 2 and xp == 2): # row not center
                    s['rows'] = [self._row_(x, y, game=self,
                                            offset=(l.XOFFSET, 0))]
        l.set_ncards_text(s['foundations'][0], 's')
        l.defaultStackGroups()
        return l

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return single ACE on bottom for _foundation_ deal.
        """
        return place.MoveToBottom(cards, place.AllAce, 1)

    def _start(self, rows=3, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea open.
          - s[foundations]: 1 ea

        .. \_start([rows=3]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRow(rows=self.s['foundations'])

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autoflip when event, else autoflip, autodrop.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        piles = super(BlackHole, self)._autopiles(event=None)
        if event is None:
            autostacks.quickplay(piles)
        return autostacks.drop_quickplay(piles)


class AllInARow(BlackHole):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 13 over 2 row . Init deal 4 card. Build none. Play none.

    _foundation_
      Pile 1 SW. Build either in rank alone wrap. Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.UD_RK_Foundation (mod=13, max_move=0,
      max_cards=52, suit=ANY)
    :cvar Stack _row_: BlackHole_RowStack (max_cards=3)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=405, name='All in a Row', game_type=(GT.GOLF |
                    GT.OPEN), decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes alone.

        .. \_create_game() -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 7 * l.XS,
                           l.YM + 2 * l._get_stack_height(6, l.YS),)
        offset = (0, l.YOFFSET)
        s['rows'] = [self._row_(x, y, game=self, offset=offset) for x, y in
                     l.col_block_iter(l.XM, l.YM, npiles=13, ncols=7, ycards=6)]
        x, y = l.XM, self.height - l.YS
        offset = (l.XOFFSET if l._get_stack_width(l.NCARDS) < self.width
                            else (self.width - 2 * l.XS) / l.NCARDS, 0)
        s['foundations'] = [self._foundation_(x, y, game=self, offset=offset)]
        l.set_ncards_text(s['foundataions'][-1], 'n')
        x = self.width - l.XS
        s['talon'] = self._talon_(x, y, game=self)
        l.defaultStackGroups()
        return l

    def _start(self, rows=4, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea open.

        .. \_start([rows=4]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()


class FourLeafClovers(AllInARow):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 13 over 2 row . Init deal 4 card. Build either in rank alone wrap.
      Play none. Base none.

    _foundation_
      Pile 1 SW. Build in rank alone wrap. Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.UD_RK_Foundation (mod=13, max_move=0,
      max_cards=52, suit=ANY)
    :cvar Stack _row_: stack.UD_RK_RowStack (mod=13, base_rank=GAP)
    :cvar Hint _hint_: hint.CautiousDefaultHint
    """
    __info__ = dict(id=267, name='Four Leaf Clovers', game_type=(GT.GOLF |
                    GT.OPEN), decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='0.80fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _foundation = Wrap(stack.RK_Foundation, suit=cs.ANY, mod=13,
                               max_move=0, max_cards=52)
    _row_ = Wrap(stack.UD_RK_RowStack, mod=13, base_rank=cs.GAP)
    _hint_ = CautiousDefaultHint


class Wasatch(game.Game):
    """
    Finish cards on _foundation without limit turn.

    _talon_
      Pile 1 SW _foundation_. Deal 3 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 3 card. Build none. Play top.

    _foundation_
      Pile 1 SW. Build either in rank alone wrap. Play none.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=INFINITE,
      num_deal=3)
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: stack.UD_RK_Foundation (mod=13, max_move=0,
      max_cards=52, suit=ANY)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=763, name='Wasatch', game_type=GT.ONE_DECK, decks=1,
                    redeals=cs.INFINITE, skill_level=SL.MOSTLY_LUCK,
                    version='1.00fc')
    __author__ = 'Skomoroh'
    __credits__ = ['Gordon Bower']
    __version__ = 7.70

    _talon_ = Wrap(stack.WasteTalon, max_rounds=cs.INFINITE,
                           num_deal=3)
    _waste_ = stack.WasteStack
    _foundation_ = Wrap(stack.UD_RK_Foundation, suit=cs.ANY, mod=13,
                                max_move=0, max_cards=52)

    def _create_game(self, **kw):
        """
        Create game format.

        Overide pile placements based on current group attributes alone.

        .. \_create_game() -> Layout
        """
        l, s = layout.Layout(self), self.s
        self.table_size = (l.XM + 3 * l.XS, l.YM + 2 * l.YS,)
        dx = l.XM + l.XS
        y = l.YM
        s['foundations'] = [self._foundation_(dx + l.HXS, y, game=self)]
        l.set_ncards_text(s['foundations'][-1], 'ne')
        y += l.YS
        s['talon'] = self._talon_(dx, y, game=self)
        l.set_ncards_text(s['talon'], 'nw')
        l.set_rounds_text(s['talon'], 'se', dx=l.XS)
        x += l.XS
        s['wastes'] = [self._waste_(dx + l.XS, y, game=self)]
        l.set_ncards_text(s['wastes'][-1], 'ne')
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: 1 ea.

        .. \_start([]) -> None
        """
        self._play_sound('deal')
        self.s['talon'].deal_cards()


class Robert(Wasatch):
    """
    Finish cards on _foundation within 3 turn.

    _talon_
      Pile 1 SW _foundation_. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 SW. Init deal base card. Build either in rank alone wrap. Play
      none.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=3, num_deal=1)
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: stack.UD_RK_Foundation (mod=13, max_move=0,
      max_cards=52, suit=ANY)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=432, name='Robert', game_type=GT.GOLF, decks=1,
                    redeals=2, skill_level=SL.LUCK, version='0.91fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = Wrap(stack.WasteTalon, max_rounds=3, num_deal=1)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: 1 ea.
          - s[foundations]: 1 ea.

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['foundations'])
        super(Robert, self)._start(**kw)


class DiamondMine(game.Game):
    """
    Finish cards of diamonds on _foundation while arranging other cards on
    _row_ within a turn.

    _row_
      Pile 13 horz S _foundation_. Init deal 4 card. Build down in rank by
      same suit with diamond none. Play seq.

    _foundation_
      Pile 1 N. Build up in rank by diamond only wrap. Play top.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.SS_Foundation (base_rank=ANY, suit=3,
      mod=13)
    :cvar Stack _row_: DiamondMine_RowStack
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=551, name='Diamond Mine', game_type=(GT.ONE_DECK |
                    GT.GNOME), decks=1, skill_level=SL.BALANCED,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = Wrap(stack.SS_Foundation, base_rank=cs.ANY, mod=13)
    _row_ = DiamondMine_RowStack
    _layout_ = FortyThievesLayout.FortyThievesLayout

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and group attributes

        .. \_create_game(rows=13, playcards=15, bases=[DIAMOND]) -> Layout
        """
        kwdefault(kw, rows=13, bases=[3], playcards=15)
        return super(DiamondMine, self)._create_game(**kw)

    def _start(self, rows=4, flip=False, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea *flip*.

        .. \_start([rows=4]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(flip=flip, frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()

    @property
    def is_complete(self):
        return (self.s['foundations'].is_filled and
                sum(s.is_filled for s in self.s['rows']) ==
                    self['suits'] - 1)


class Dolphin(game.Game):
    """
    Finish cards on _foundation within a turn.

    :CVariables:
      _layout_ : pysollib.layout.Layout
        .. pysollib.layouts.freecell.FreecellLayout
        - row [8 horz S reserve and foundation, vert offset]
        - talon [1 SE, no offset]
        - foundation [1 NE, no offset]
        - reserve [4 horz NW, no offset]

    _row_
      Pile 8 horz S _reserve_ and _foundation_. Init deal [7, 7, 7, 7, 6, 6,
      6, 6] open card. Build none. Play top.

    _reserve_
      Pile 4 horz NW. Build any 1 card. Play top.

    _foundation_
      Pile 1 NE. Build up in rank alone wrap. Play top.
    ------
    :cvar Layout _layout_: layout.FreeCellLayout
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.RK_Foundation (base_rank=ANY,
                                mod=13, max_cards=52)
    :cvar Stack _row_: stack.BasicRowStack
    :cvar Stack _reserve_: stack.BasicRowStack
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=661, name='Dolphin', game_type=(GT.GOLF | GT.ORIGINAL),
                    decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='0.94fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _layout_ = FreeCellLayout.FreeCellLayout
    _talon_ = stack.InitialDealTalon
    _foundation_ = Wrap(stack.RK_Foundation, base_rank=cs.ANY,
                                mod=13, max_cards=52)
    _row_ = stack.BasicRowStack
    _reserve_ = stack.ReserveStack

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and group attributes

        .. \_create_game(rows=8, reserves=4, playcards=6, bases=[ANY]) -> Layout
        """
        kwdefault(kw, rows=8, reserves=4, playcards=6, bases=[cs.ANY])
        l, s = super(Dolphin, self)._create_game(**kw), self.s
        l.set_ncards_text(s['foundations'][-1], 'ne')
        return l

    def _start(self, rows=6):
        """
        Deal initial cards for new game.
          - s[rows]: ~ *rows* ea open.

        .. \_start([rows=6]) -> None
        """
        for i in range(rows - 2):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()
        self.s['talon'].dealRowAvail()


class DoubleDolphin(Dolphin):
    """
    Finish cards on _foundation within a turn.

    :CVariables:
      _layout_ : pysollib.layout.Layout
        .. pysollib.layouts.freecell.FreecellLayout
        - row [8 horz S reserve and foundation, vert offset]
        - talon [1 SE, no offset]
        - foundation [1 NE, no offset]
        - reserve [4 horz NW, no offset]

    _row_
      Pile 8 horz S _reserve_ and _foundation_. Init deal [11, 11, 11, 11, 10,
      10, 10, 10] open card. Build none. Play top.

    _reserve_
      Pile 4 horz NW. Build any 1 card. Play top.

    _foundation_
      Pile 1 NE. Build up in rank alone wrap. Play top.
    ------
    :cvar Layout _layout_: layout.FreeCellLayout
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.RK_Foundation (base_rank=ANY,
                                mod=13, max_cards=52)
    :cvar Stack _row_: stack.BasicRowStack
    :cvar Stack _reserve_: stack.BasicRowStack
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=662, name='Double Dolphin', game_type=(GT.GOLF |
                    GT.ORIGINAL), decks=2, version='0.94fc',
                    skill_level=SL.MOSTLY_SKILL, altnames=(
                    'Dolphin (2 Decks)',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and pile attributes alone.

        .. \_create_game(rows=10, reserves=5, playcards=10, ndecks=1) -> Layout
        """
        kwdefault(kw, rows=10, reserves=5, playcards=10, ndecks=1)
        super(DoubleDolphin, self)._create_game(**kw)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: ~ *rows* ea open.

        .. \_start([rows=11]) -> None
        """
        kw.setdefault('rows', 11)
        super(DoubleDolphin, self)._start(**kw)


class Waterfall(game.Game):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 8 horz N. Init deal 4 open card. Build down in rank alone. Play
      seq.

    _talon_
      Pile 1 SW. Deal 1 card on _row_.

    _foundation_
      Pile 1 S. Build up in rank by same suit wrap rank and suit. Play top.
    ------
    :cvar Stack _talon_: stack.DealRowTalon
    :cvar Stack _foundation_: Waterfall_Foundation (max_cards=104,
      base_suit=0, base_rank=ACE)
    :cvar Stack _row_: stack.RK_RowStack
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=709, name='Waterfall', game_type=(GT.TWO_DECK |
                    GT.ORIGINAL), decks=2, version='1.00fc',
                    skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.DealRowTalon
    _foundation_ = Wrap(Waterfall_Foundation, max_cards=104,
                                base_suit=0, base_rank=cs.ACE)
    _row_ = stack.RK_RowStack

    def _create_game(self, rows=8, playcards=20, **kw):
        """
        Create game format.

        Override pile placements based on current attributes alone.

        .. \_create_game(rows=8, playcards=20) -> Layout
        """
        l, s = layout.Layout(self), self.s
        rows = kw.get('rows', 8)
        self.table_size = (l.XM + rows * l.XS,
                     l.YM + l._get_stack_height(kw.get('playcards', 20), l.YS),)
        center = lambda p: l.XM + (rows - p) * l.HXS
        s['rows'] = [self._row_(center(rows), l.YM, game=self)
                        for x in l.row_iter(l.XM, rows)]
        y = self.height - l.YS
        s['foundations'] = [self._foundation_(center(1), y, game=self)]
        l.set_misc_text(s['foundation'][-1], 'se')
        x = self.width - l.XS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'sw')
        l.defaultStackGroups()
        return l

    def _start(self, rows=4, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea open.

        .. \_start([rows=4]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()


class ThirtyTwoCards(game.Game):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 32 over 4 row S _foundations_. Init deal 1 card. Build none. Play
      top. Gap deals 1 card from _talon_.

    _talon_
      Pile 1 NW.

    _foundation_
      Pile 8 N. Build in rank by same suit. E up. W down. Play top.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _foundation_: stack.SS_Foundation,
      stack.SS_Foundation (base_rank=KING, dir=-1)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=728, name='Thirty Two Cards', game_type=GT.TWO_DECK,
                    decks=2, skill_level=SL.LUCK,
                    version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.Talon
    _foundations_ = (stack.SS_Foundation,
                     Wrap(stack.SS_Foundation, base_rank=cs.KING, dir=-1),)
    _row_ = RowStack
    _layout_ = KlondikeLayout.KlondikeLayout

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and pile attributes alone.

        .. \_create_game(rows=32, ncols=8, playcards=0) -> Layout
        """
        kwdefault(kw, rows=32, ncols=8, playcards=0)
        return super(ThirtyTwoCards, self)._create_game(*kw)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: 1 ea.

        .. \_start([]) -> None
        """
        self._play_sound('deal')
        self.s['talon'].dealRow()

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill _row_ from _talon_.

        *Parameters*:
          stack : Stack
           pile just played
        """
        fill.fromTalon(self, stack, self.s['rows'])

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autoflip when event, else autoflip, autodrop.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        piles = super(ThirtyTwoCards, self)._autopiles(event=None)
        if event is None:
            autostacks.quickplay(piles)
        return autostacks.drop_quickplay(piles)


class Vague(ThirtyTwoCards):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 18 over 3 row S _foundations_. Init deal 1 card. Build none. Play
      top. Gap deals 1 card from _talon_.

    _talon_
      Pile 1 NW. Init flip top.

    _foundation_
      Pile 8 N. Build up in rank by same suit wrap. Base any. Play top.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _foundation_: stack.SS_Foundation (base_rank=ANY, mod=13)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=720, name='Vague', game_type=GT.ONE_DECK, decks=1,
                    skill_level=SL.MOSTLY_LUCK, version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _foundations_ = (Wrap(stack.SS_Foundation, base_rank=cs.ANY, mod=13),)

    def _create_game(self, **kw):
        """
        Create game format.

        Augment pile placements based on current form and pile attributes alone.

        .. \_create_game(rows=18, ncols=6, playcards=0) -> Layout
        """
        kwdefault(rows=18, ncols=6)
        super(Vague, self)._create_game(**kw)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: 1 ea.
          - s[talon]: open.

        .. \_start([]) -> None
        """
        super(Vague, self)._start(**kw)
        self.flip(self.s['talon'])


class DevilsSolitaire(game.Game):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 8 horz S _foundations_. Init deal 1 card. Build none. Play
      top. Gap deals 1 card from _waste_.

    _reserve_
      Pile 1 center between _row_. Init deal 8 card. Build none. Play
      top.

    _talon_
      Pile 1 S. Deal 1 card on _waste_.

    _waste_
      Pile 1 W. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 2, 1 N 1 NE. Build N up in rank alone wrap NE none. N auto drop
      full seq plus _reserve_ top on NW. Play N top NW none. Base N +1
      _reserve_ top.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _waste_: DropWasteStack
    :cvar Stack _foundation_: DropFoundation (base_rank=ANY, mod=13),
      stack.SingleFoundation
    :cvar Stack _row_: RowStack
    :cvar Stack _reserve_: stack.OpenStack
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=723, name='Devilʼs Solitaire', game_type=GT.TWO_DECK,
                    decks=2, redeals=2, skill_level=SL.BALANCED,
                    altnames=('Banner',), version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = Wrap(stack.WasteStack, max_rounds=3)
    _waste_ = DropWasteStack
    _foundations_ = (Wrap(DropFoundation, base_rank=cs.ANY, mod=13),
                     stack.SingleFoundation,)
    _row_ = RowStack
    _reserve_ = stack.OpenStack

    def _create_game(self, **kw):
        """
        Create game format.

        Override pile placements based on current pile attributes alone.

        .. \_create_game(rows=8, playcards=8) -> Layout
        """
        l, s = layout.Layout(self), self.s
        rows = kw.get('rows', 8)
        self.table_size = (l.XM + (rows + 1) * l.XS, l.YM + l.YS +
                           l._get_stack_height(kw.get('playcards', 8), l.YS),)
        center = lambda p: l.XM + (rows + 1 - p) * l.XS / 2
        y = l.YM
        s['foundations'] = [self._foundations_[i](x, y, game=self)
                               for i, x in enumerate(
                               l.row_iter(center(1), npiles=2, dx=rows * l.XS))]
        l.set_misc_text(s['foundations'][0], 'nw')
        l.set_ncards_text(s['foundations'][1], 'nw')
        y += l.YS
        for x in l.row_iter(l.XM, rows + 1):
            if x == center(1):
                s['reserves'].append(self._reserve_(x, y, game=self,
                                                    offset=(0, l.YOFFSET)))
            else:
                s['rows'].append(self._row_(x, y, game=self))
        x, y = center(2), self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'n')
        l.set_rounds_text(s['talon'], 'nnn')
        x -= l.XS
        s['wastes'].append(self._waste_(x, y, game=self))
        l.set_ncards_text(s['wastes'][-1], 'n')
        l.defaultStackGroups()
        return l

    def _start(self, reserves=8):
        """
        Deal initial cards for new game.
          - s[reserves]: *reserves* ea open.

        .. \_start([reserves=8]) -> None
        """
        for i in range(reserves):
            self.s['talon'].dealRow(rows=self.s['reserves'], frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill _row_ from _waste_.

        *Parameters*:
          stack : Stack
            pile just played
        """
        fill.fromWaste(self, stack, self.s['rows'])

    @state.not_state('is_preview')
    def _update_text(self):
        # TODO: Move to pile method
        r = self.s['reserves'][0]
        t = '' if not r.cards else self.RANKS[(r.TOPCARD['rank'] + 1) % 13]
        self.s['foundations'][0].texts['misc'].config(text=t)


class Beacon(game.Game):
    """
    Finish cards on _foundation within a turn.

    _row_
      Pile 8 horz S _foundation_. Init deal 5 card. Build none. Play
      top. Gap deals 1 card from _talon_.

    _talon_
      Pile 1 NW.

    _foundation_
      Pile 2, 1 N 1 NE. Build N up in rank alone wrap NE none. N auto drop
      full seq plus _reserve_ top on NW. Play N top NW none. Base N +1
      _reserve_ top.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _foundation_: stack.RK_Foundation (base_rank=ANY,
      max_cards=52, mod=13)
    :cvar Stack _row_: stack.RK_RowStack (base_rank=GAP, mod=13)
    :cvar Hint _hint_: hint.defaultHint
    """
    __info__ = dict(id=764, name='Beacon', game_type=(GT.ONE_DECK |
                    GT.ORIGINAL), decks=1, version='2.00fc',
                    skill_level=SL.MOSTLY_SKILL)
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.Talon
    _foundation_ = Wrap(stack.RK_Foundation, max_cards=52, mod=13)
    _row_ = Wrap(stack.RK_RowStack, base_rank=cs.GAP, mod=13)
    _layout_ = FortyThievesLayout.FortyThievesLayout

    def _create_game(self,  **kw):
        """
        Create game format.

        Override pile placements based on current pile attributes alone.

        .. \_create_game(rows=8, playcards=8) -> Layout
        """
        kwdefault(kw, rows=8, playcards=12, bases=[cs.ANY])
        l, s = super(Beacon, self)._create_game(**kw)
        l.set_ncards_text(s['foundations'][-1], 'ne')
        return l

    def _start(self, rows=4, **kw):
        """
        Deal initial cards for new game.
          - s[rows]: *rows* ea open.

        .. \_start([rows=4]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow()

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill _row_ from _waste_.

        *Parameters*:
          stack : Stack
            pile just played
        """
        fill.fromTalon(self, stack, self.s['rows'])


PROVIDES = [Golf, DeadKingGolf, RelaxedGolf, BlackHole, FourLeafClovers,
            AllInARow, Robert, DiamondMine, Dolphin, DoubleDolphin, Waterfall,
            Vague, DevilsSolitaire, ThirtyTwoCards, Wasatch, Beacon]